
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.stuffrepos.bricknetcenter.provider;

import java.util.LinkedList;
import java.util.List;
import net.stuffrepos.bricknetcenter.database.DataManager;
import net.stuffrepos.bricknetcenter.database.schema.HttpRequest;
import net.stuffrepos.bricknetcenter.http.HttpDownloader;
import net.stuffrepos.util.listeners.Listener;
import net.stuffrepos.util.listeners.ListenerManager;
import net.stuffrepos.util.listeners.SourceVariableListenerManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public class HttpProviderManager {

    private static final Log log = LogFactory.getLog(HttpProviderManager.class);
    private static final int MAX_DOWNLOADER_THREAD = 4;
    private static HttpProviderManager instance = new HttpProviderManager();
    private MainThread mainThread;

    public static HttpProviderManager getInstance() {
        return instance;
    }
    private SourceVariableListenerManager<HttpRequest> requestListeners =
            new SourceVariableListenerManager<HttpRequest>();
    private ListenerManager<HttpProviderManager> stateChangedListeners =
            new ListenerManager<HttpProviderManager>(this);
    private State state = State.Stopped;

    protected HttpProviderManager() {
    }
    private List<HttpProvider> httpProviders = new LinkedList<HttpProvider>();

    public void addHttpProvider(HttpProvider provider) {
        this.httpProviders.add(provider);
    }
    
    public State getState() {
        return state;
    }

    public void start() throws Exception {
        new Thread() {

            @Override
            public void run() {
                internalStart();
            }
        }.start();
    }

    private synchronized void internalStart() {
        if (mainThread == null) {
            try {
                DataManager.getInstance().getHttpRequestManager().resetHttpRequests();
                for (HttpProvider provider : httpProviders) {
                    for (net.stuffrepos.bricknetcenter.http.HttpRequest request : provider.getRootRequests()) {
                        DataManager.getInstance().getHttpRequestManager().addHttpRequest(request);
                    }
                }
                mainThread = new MainThread();
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            mainThread.start();
            state = HttpProviderManager.State.Running;
            stateChangedListeners.fireChange();
        }
    }

    public synchronized void pause() {
        new Thread() {

            @Override
            public void run() {
                internalPause();
            }
        }.start();

    }

    private void internalPause() {
        if (mainThread != null) {
            try {
                mainThread.stopExecution();
                mainThread = null;
                state = HttpProviderManager.State.Paused;
                stateChangedListeners.fireChange();
            } catch (InterruptedException ex) {
                log.warn("", ex);
            }
        }
    }

    public void stop() {
        new Thread() {

            @Override
            public void run() {
                internalStop();

            }
        }.start();
    }

    private void internalStop() {
        if (mainThread != null) {
            mainThread.stopExecutionImmediately();
            mainThread = null;
            state = HttpProviderManager.State.Stopped;
            try {
                DataManager.getInstance().getHttpRequestManager().resetHttpRequests();
            } catch (Exception ex) {
                log.warn("", ex);
            }
            stateChangedListeners.fireChange();
        }
    }

    public void addRequestListener(Listener<HttpRequest> listener) {
        requestListeners.addListener(listener);
    }

    public void addStateChangeListener(Listener<HttpProviderManager> listener) {
        stateChangedListeners.addListener(listener);
    }

    private class MainThread extends Thread {

        List<DownloaderThread> downloaders;

        @Override
        public void run() {

            downloaders = new LinkedList<DownloaderThread>();
            for (int i = 0; i < MAX_DOWNLOADER_THREAD; ++i) {
                DownloaderThread downloader = new DownloaderThread();
                downloaders.add(downloader);
                downloader.start();
            }

            for (DownloaderThread downloader : downloaders) {
                try {
                    downloader.join();
                } catch (InterruptedException ex) {
                    log.warn(downloader.getName(), ex);
                }
            }
        }

        private void stopExecution() throws InterruptedException {
            for (DownloaderThread downloader : downloaders) {
                downloader.stopExecution();
            }
            for (DownloaderThread downloader : downloaders) {
                downloader.join();
            }
        }

        private void stopExecutionImmediately() {
            for (DownloaderThread downloader : downloaders) {
                downloader.stopExecutionImmediately();
            }
        }
    }

    private class DownloaderThread extends Thread {

        private boolean run = true;
        private HttpDownloader downloader = new HttpDownloader();

        @Override
        public void run() {

            while (run) {

                try {
                    if (log.isTraceEnabled()) {
                        log.trace("Getting next http request...");
                    }
                    HttpRequest request = DataManager.getInstance().getHttpRequestManager().getNextHttpRequest();

                    if (request != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("HttpRequest found: " + request.getUrlSpecification());
                        }
                        try {
                            requestListeners.fireChange(request);
                            downloader.download(request.getHttpRequest());
                            DataManager.getInstance().getHttpRequestManager().markAsCompleted(request);
                            requestListeners.fireChange(request);
                        } catch (Exception ex) {
                            log.warn("fail on download request " + request.getUrlSpecification(), ex);
                            DataManager.getInstance().getHttpRequestManager().markAsFailed(request);
                            requestListeners.fireChange(request);
                        }
                    } else {
                        int seconds = (int) (3 + Math.random() * 10);
                        if (log.isTraceEnabled()) {
                            log.trace("No new HttpRequest found. Sleeping for " + seconds + " seconds...");
                        }
                        sleep(seconds * 1000);

                    }
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }

        private void stopExecution() {
            run = false;
        }

        private void stopExecutionImmediately() {
            run = false;
            downloader.stopExecutionImmediately();
        }
    }

    public enum State {

        Running,
        Stopped,
        Paused
    }
}
